package com.cloud.common.feign;

import com.cloud.common.loadbalance.ServiceInstanceStatus;
import com.cloud.common.loadbalance.ServiceInstanceStatusManager;
import feign.Client;
import feign.Request;
import feign.Response;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.HttpStatus;

import java.io.IOException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

/**
 * feign代理客户端
 *
 * @author fengwenjin
 */
@Slf4j
public class FeignProxyClient implements Client {
    private final Client delegate;
    private final LoadBalancerClient loadBalancerClient;

    public FeignProxyClient(Client delegate, LoadBalancerClient loadBalancerClient) {
        this.delegate = delegate;
        this.loadBalancerClient = loadBalancerClient;
    }

    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        final URI originalUri = URI.create(request.url());
        String serviceId = originalUri.getHost();
        ServiceInstance instance = loadBalancerClient.choose(serviceId);
        if (instance == null) {
            String message = serviceId + " service unavailable";
            log.warn(message);
            return Response.builder().request(request)
                    .status(HttpStatus.SERVICE_UNAVAILABLE.value())
                    .body(message, StandardCharsets.UTF_8).build();
        }

        // 获取实例状态进行统计，并远程调用
        ServiceInstanceStatus instanceStatus = ServiceInstanceStatusManager.get(instance);
        instanceStatus.incrementActiveRequestsCount();
        try {
            String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
            Request newRequest = buildRequest(request, reconstructedUrl);
            return delegate.execute(newRequest, options);
        } catch (Exception e) {
            if (e instanceof SocketException || e instanceof SocketTimeoutException || e instanceof TimeoutException) {
                instanceStatus.incrementConnectionFailureCount();
            }
            throw e;
        } finally {
            instanceStatus.decrementActiveRequestsCount();
        }
    }

    protected Request buildRequest(Request request, String reconstructedUrl) {
        return Request.create(request.httpMethod(), reconstructedUrl, request.headers(),
                request.body(), request.charset(), request.requestTemplate());
    }
}
